package cn.flying.base.process.service;

import javax.annotation.Resource;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowElement;
import org.activiti.bpmn.model.Process;
import org.activiti.bpmn.model.ReceiveTask;
import org.activiti.bpmn.model.ScriptTask;
import org.activiti.bpmn.model.ServiceTask;
import org.activiti.bpmn.model.Task;
import org.activiti.bpmn.model.UserTask;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.ProcessEngineConfiguration;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.repository.ProcessDefinitionQuery;
import org.activiti.image.ProcessDiagramGenerator;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;

import cn.flying.base.common.enums.RtCodeEnum;
import cn.flying.base.common.exception.BizException;
import cn.flying.base.common.utils.SnowflakeUtil;
import cn.flying.base.common.utils.StringUtils;
import cn.flying.base.common.vo.PageRequest;
import cn.flying.base.common.vo.PageResponse;
import cn.flying.base.core.common.utils.Ft;
import cn.flying.base.process.bo.ProcDefinition;
import cn.flying.base.process.domain.cf.CfProcConfig;
import cn.flying.base.process.domain.cf.CfProcConfigExample;
import cn.flying.base.process.domain.cf.CfProcNode;
import cn.flying.base.process.domain.cf.CfTaskPosition;
import cn.flying.base.process.domain.cf.CfTaskPositionExample;
import cn.flying.base.process.mapper.cf.CfProcConfigMapper;
import cn.flying.base.process.mapper.cf.CfProcNodeMapper;
import cn.flying.base.process.mapper.cf.CfTaskPositionMapper;

/**
 * @description: 流程定义服务
 * @author: lvyong
 * @date: 2023年02月16日 10:45
 * @version: 1.0
 */
@Service
public class ProcessDefinitionService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Resource
    private ProcessEngine processEngine;
    @Resource
    private RepositoryService repositoryService;
    @Resource
    private CfProcConfigMapper cfProcConfigMapper;
    @Resource
    private CfTaskPositionMapper cfTaskPositionMapper;
    @Resource
    private CfProcNodeMapper cfProcNodeMapper;

    /**
     * 部署流程定义
     *
     * @param file
     * @param key
     * @param name
     * @param tenantId
     * @return
     * @throws Exception
     */
    @Transactional(rollbackFor = Exception.class)
    public String deployment(MultipartFile file, String key, String name, String tenantId) throws Exception {
        String originalFilename = file.getOriginalFilename();
        logger.info("部署流程图【{}】", originalFilename);
        InputStream inputStream = file.getInputStream();
        Deployment deployment = repositoryService.createDeployment()
                .addInputStream(originalFilename, inputStream)
                .key(key).name(name).tenantId(tenantId)
                .deploy();
        logger.info("部署流程成功！流程部署ID；【{}】，流程部署key：【{}】，流程部署名称：【{}】，流程部署时间：【{}】",
                deployment.getId(), deployment.getKey(), deployment.getName(), DateFormatUtils.format(deployment.getDeploymentTime(), "yyyy-MM-dd HH:mm:ss"));

        //解析流程节点保存
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StringUtils.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        ProcessDefinition definition = processDefinitionQuery.deploymentId(deployment.getId()).singleResult();
        List<CfProcNode> nodes = getAllNode(definition.getId());
        for (CfProcNode node : nodes) {
            node.setProcDefId(definition.getId());
            node.setProcDefKey(definition.getKey());
            node.setVersion(String.valueOf(definition.getVersion()));
            node.setTenantId(definition.getTenantId());
            cfProcNodeMapper.insert(node);
        }

        //将上一个版本配置的节点权限复制与新版本建立关联
//        addTaskRoles(definition.getId());
        return deployment.getId();
    }

    /**
     * 解析流程图，保存所有的节点
     *
     * @param processDefId
     * @return
     */
    private List<CfProcNode> getAllNode(String processDefId) {
        List<CfProcNode> list = new ArrayList<>();
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefId);
        Process process = bpmnModel.getProcessById(processDefId.split(":")[0]);
        Collection<FlowElement> flowElements = process.getFlowElements();
        Iterator<FlowElement> iterator = flowElements.iterator();
        CfProcNode procNode;

        Task task;
        while (iterator.hasNext()) {
            procNode = new CfProcNode();
            FlowElement flowElement = iterator.next();
            if (flowElement instanceof UserTask) {
                task = (UserTask) flowElement;
                procNode.setNodeId(task.getId());
                procNode.setNodeName(task.getName());
                procNode.setNodeType("UserTask");
                procNode.setRemark(task.getDocumentation());
                list.add(procNode);
            }
            if (flowElement instanceof ServiceTask) {
                task = (ServiceTask) flowElement;
                procNode.setNodeId(task.getId());
                procNode.setNodeName(task.getName());
                procNode.setNodeType("ServiceTask");
                procNode.setRemark(task.getDocumentation());
                list.add(procNode);
            }
            if (flowElement instanceof ReceiveTask) {
                task = (ReceiveTask) flowElement;
                procNode.setNodeId(task.getId());
                procNode.setNodeName(task.getName());
                procNode.setNodeType("ReceiveTask");
                procNode.setRemark(task.getDocumentation());
                list.add(procNode);
            }
            if (flowElement instanceof ScriptTask) {
                task = (ScriptTask) flowElement;
                procNode.setNodeId(task.getId());
                procNode.setNodeName(task.getName());
                procNode.setNodeType("ScriptTask");
                procNode.setRemark(task.getDocumentation());
                list.add(procNode);
            }
        }
        return list;
    }

    /**
     * 添加节点权限
     *
     * @param processDefId
     */
//    private void addTaskRoles(String processDefId) {
//        String[] keys = processDefId.split(":");
//        //查询本次版本的上一次定义文件
//        ProcessDefinition definition = repositoryService.createProcessDefinitionQuery().processDefinitionKey(keys[0]).processDefinitionVersionLowerThan(Integer.valueOf(keys[1])).orderByProcessDefinitionVersion().desc().singleResult();
//        List<CfTaskRole> taskRoles = processTaskRoleMapper.getProcDefTaskRoles(definition.getId());
//        if (CollectionUtils.isEmpty(taskRoles)) {
//            return;
//        }
//        CfTaskRole cfTaskRole;
//        for (CfTaskRole taskRole : taskRoles) {
//            cfTaskRole = new CfTaskRole();
//            cfTaskRole.setTaskId(taskRole.getTaskId());
//            cfTaskRole.setTaskName(taskRole.getTaskName());
//            cfTaskRole.setTaskType(taskRole.getTaskType());
//            cfTaskRole.setRoleId(taskRole.getRoleId());
//            cfTaskRole.setRoleGroupId(taskRole.getRoleGroupId());
//            cfTaskRole.setProcDefId(processDefId);
//            cfTaskRole.setProcDefKey(taskRole.getProcDefKey());
//            cfTaskRole.setTenantId(taskRole.getTenantId());
//            processTaskRoleMapper.insert(cfTaskRole);
//        }
//    }

    /**
     * 查询流程部署定义列表
     *
     * @param pageRequest
     * @return
     */
    public PageResponse getProcessDefinitionPageList(PageRequest<ProcDefinition> pageRequest) {
        Integer pageNum = pageRequest.getPageNum();
        Integer pageSize = pageRequest.getPageSize();
        ProcDefinition param = pageRequest.getParam();
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StringUtils.isNotBlank(param.getTenantId())) {
            processDefinitionQuery.processDefinitionTenantId(param.getTenantId());
        }
        if (StringUtils.isNotBlank(param.getKey())) {
            processDefinitionQuery.processDefinitionKeyLike("%" + param.getKey() + "%");
        }
        if (StringUtils.isNotBlank(param.getName())) {
            processDefinitionQuery.processDefinitionNameLike("%" + param.getName() + "%");
        }

        processDefinitionQuery.orderByProcessDefinitionKey().orderByProcessDefinitionVersion().desc();
        List<ProcessDefinition> processDefinitions = processDefinitionQuery.listPage((pageNum - 1) * pageSize, pageSize);
        Iterator<ProcessDefinition> iterator = processDefinitions.iterator();
        ProcDefinition procDefinition;
        List<ProcDefinition> list = new ArrayList<>();
        while (iterator.hasNext()) {
            ProcessDefinition definition = iterator.next();
            procDefinition = new ProcDefinition();
            mapProcessDefinition(procDefinition, definition);
            list.add(procDefinition);
        }
        return new PageResponse((long) list.size(), pageNum, list);
    }

    private void mapProcessDefinition(ProcDefinition procDefinition, ProcessDefinition definition) {
        procDefinition.setId(definition.getId());
        procDefinition.setCategory(definition.getCategory());
        procDefinition.setKey(definition.getKey());
        procDefinition.setName(definition.getName());
        procDefinition.setDescription(definition.getDescription());
        procDefinition.setResource(definition.getResourceName());
        procDefinition.setDiagramResourceName(definition.getDiagramResourceName());
        procDefinition.setVersion(String.valueOf(definition.getVersion()));
        procDefinition.setSuspended(definition.isSuspended());
        procDefinition.setDeploymentId(definition.getDeploymentId());
        procDefinition.setTenantId(definition.getTenantId());
    }


    /**
     * 挂起流程
     *
     * @param processDefId
     * @param tenantId
     */
    @Transactional(rollbackFor = Exception.class)
    public void suspendProcessDefinition(String processDefId, String tenantId) {
        String[] keys = processDefId.split(":");
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().processDefinitionKey(keys[0]).processDefinitionVersion(Integer.parseInt(keys[1]));
        if (StringUtils.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
        repositoryService.suspendProcessDefinitionById(processDefinition.getId());
        logger.info("挂起流程，流程定义ID：【{}】，租户：【{}】", processDefId, tenantId);
    }

    /**
     * 激活流程
     *
     * @param processDefId
     * @param tenantId
     */
    @Transactional(rollbackFor = Exception.class)
    public void activeProcessDefinition(String processDefId, String tenantId) {
        String[] keys = processDefId.split(":");
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().processDefinitionKey(keys[0]).processDefinitionVersion(Integer.parseInt(keys[1]));
        if (StringUtils.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
        repositoryService.activateProcessDefinitionById(processDefinition.getId());
        logger.info("激活流程，流程定义ID：【{}】，租户：【{}】", processDefId, tenantId);
    }

    /**
     * 删除流程部署
     * 会同时删除流程实例
     *
     * @param deploymentId
     * @param tenantId
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteDeployment(String deploymentId, String tenantId, boolean cascade) {
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StringUtils.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        List<ProcessDefinition> definitions = processDefinitionQuery.deploymentId(deploymentId).list();

        Optional.ofNullable(definitions).orElseThrow(() -> new BizException(RtCodeEnum.R90000.getCode(), "要删除的流程部署不存在！"));

        Set<String> set = definitions.stream().map(ProcessDefinition::getId).collect(Collectors.toSet());

        if (!CollectionUtils.isEmpty(set)) {
            throw new BizException(RtCodeEnum.R90000.getCode(), "不能删除有正在运行的流程实例的流程定义！");
        }

        if (Objects.isNull(cascade)) {
            cascade = true;
        }
        //同时删除流程实例
        repositoryService.deleteDeployment(deploymentId, cascade);
        logger.info("删除流程部署，部署ID：【{}】，租户：【{}】", deploymentId, tenantId);
    }

    /**
     * 查看部署定义流程图
     *
     * @param processDefId
     * @return
     */
    public byte[] getProcessDiagram(String processDefId) {
        //使用宋体
        String fontName = "宋体";
        InputStream ins = null;
        try {
            //获取BPMN模型对象
            BpmnModel model = repositoryService.getBpmnModel(processDefId);
            ins = processEngine.getProcessEngineConfiguration()
                    .getProcessDiagramGenerator()
                    .generateDiagram(model, "png", new ArrayList<String>(), new ArrayList<String>(),
                            fontName, fontName, fontName, null, 1.0);
            return IOUtils.toByteArray(ins);
        } catch (Exception e) {
            logger.error("生成流程图失败", e);
            throw new BizException(RtCodeEnum.R_ERROR.getCode(), "流程图生成失败！");
        } finally {
            if (ins != null) {
                try {
                    ins.close();
                } catch (IOException e) {
                    logger.error("关闭流失败", e);
                }
            }
        }
    }

    /**
     * 查询最新的流程定义
     *
     * @param tenantId
     * @return
     */
    public Map<String, String> getProcDefKeys(String tenantId) {
        Map<String, String> map = new HashMap<>();
        //查询激活的流程定义
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().active();
        if (StringUtils.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        List<ProcessDefinition> definitions = processDefinitionQuery.latestVersion().list();
        Iterator<ProcessDefinition> iterator = definitions.iterator();
        while (iterator.hasNext()) {
            ProcessDefinition processDefinition = iterator.next();
            map.put(processDefinition.getKey(), processDefinition.getName());
        }
        return map;
    }

    /**
     * 查询所有的流程业务配置
     *
     * @param pageRequest
     * @return
     */
    public PageResponse getProcessBizConfigPageList(PageRequest<CfProcConfig> pageRequest) {
        Integer pageNum = pageRequest.getPageNum();
        Integer pageSize = pageRequest.getPageSize();
        CfProcConfig config = pageRequest.getParam();
        CfProcConfigExample example = new CfProcConfigExample();
        CfProcConfigExample.Criteria criteria = example.createCriteria();
        if (Ft.String.isNotBlank(config.getProcDefKey())) {
            criteria.andProcDefKeyEqualTo(config.getProcDefKey());
        }
        if (Ft.String.isNotBlank(config.getTenantId())) {
            criteria.andTenantIdEqualTo(config.getTenantId());
        }
        PageHelper.offsetPage((pageNum - 1) * pageSize, pageSize);
        Page<CfProcConfig> page = (Page<CfProcConfig>) cfProcConfigMapper.selectByExample(example);
        return new PageResponse<>(page.getTotal(), pageNum, page.getResult());
    }

    /**
     * 根据主键ID查询流程业务配置
     *
     * @param id
     * @return
     */
    public CfProcConfig findProcBizConfigById(Long id) {
        return cfProcConfigMapper.selectByPrimaryKey(id);
    }

    /**
     * 保存流程业务配置
     *
     * @param config
     */
    @Transactional(rollbackFor = Exception.class)
    public void saveBizConfig(CfProcConfig config) {
        if (config.getId() == null) {
            config.setId(SnowflakeUtil.genId());
            cfProcConfigMapper.insert(config);
        } else {
            cfProcConfigMapper.updateByPrimaryKey(config);
        }
    }

    /**
     * 删除流程业务配置
     *
     * @param config
     */
    @Transactional(rollbackFor = Exception.class)
    public void deleteBizConfig(CfProcConfig config) {
        cfProcConfigMapper.deleteByPrimaryKey(config.getId());
    }

    /**
     * 查询最新流程定义列表
     *
     * @param pageRequest
     * @return
     */
    public PageResponse getProcessDefinitionLatestVersionPageList(PageRequest<String> pageRequest) {
        Integer pageNum = pageRequest.getPageNum();
        Integer pageSize = pageRequest.getPageSize();
        String tenantId = pageRequest.getParam();
        ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery();
        if (StringUtils.isNotBlank(tenantId)) {
            processDefinitionQuery.processDefinitionTenantId(tenantId);
        }
        List<ProcessDefinition> processDefinitions = processDefinitionQuery.latestVersion().listPage((pageNum - 1) * pageSize, pageSize);
        processDefinitions = Optional.ofNullable(processDefinitions).orElse(new ArrayList<>());

        List<ProcDefinition> list = processDefinitions.stream().map(definition -> {
            ProcDefinition procDefinition = new ProcDefinition();
            mapProcessDefinition(procDefinition, definition);
            return procDefinition;
        }).collect(Collectors.toList());

        return new PageResponse((long) list.size(), pageNum, list);
    }

    /**
     * 查询流程任务节点的权限列表
     *
     * @param pageRequest
     * @return
     */
    public PageResponse getProcessTaskIdentityPageList(PageRequest<CfTaskPosition> pageRequest) {
        Integer pageNum = pageRequest.getPageNum();
        Integer pageSize = pageRequest.getPageSize();
        CfTaskPosition taskPosition = pageRequest.getParam();
        CfTaskPositionExample example = new CfTaskPositionExample();
        CfTaskPositionExample.Criteria criteria = example.createCriteria();
        if (org.springframework.util.StringUtils.hasText(taskPosition.getProcDefId())) {
            criteria.andProcDefIdEqualTo(taskPosition.getProcDefId());
        }
        if (org.springframework.util.StringUtils.hasText(taskPosition.getTenantId())) {
            criteria.andTenantIdEqualTo(taskPosition.getTenantId());
        }
        PageHelper.offsetPage((pageNum - 1) * pageSize, pageSize);
        Page<CfTaskPosition> page = (Page<CfTaskPosition>) cfTaskPositionMapper.selectByExample(example);
        return new PageResponse(page.getTotal(), pageNum, page.getResult());
    }

    /**
     * 查询任务节点的权限
     * @param id
     * @return
     */
    public CfTaskPosition findProcTaskPositionById(Long id) {
        return cfTaskPositionMapper.selectByPrimaryKey(id);
    }

    /**
     * 根据流程定义ID查询所有的人工节点
     *
     * @param processDefId
     * @return
     */
    public Map<String, String> getAllUserTask(String processDefId) {
        String[] keys = processDefId.split(":");
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefId);
        Process process = bpmnModel.getProcessById(keys[0]);
        Collection<FlowElement> flowElements = process.getFlowElements();
        Iterator<FlowElement> iterator = flowElements.iterator();
        Map<String, String> tasks = new HashMap<>();
        while (iterator.hasNext()) {
            FlowElement flowElement = iterator.next();
            if (flowElement instanceof UserTask) {
                UserTask userTask = (UserTask) flowElement;
                tasks.put(userTask.getId(), userTask.getName());
            }
        }
        return tasks;
    }

    /**
     * 查询节点权限配置
     *
     * @param pageRequest
     * @return
     */
//    public PageResponse getProcDefTaskRolesPageList(PageRequest<String> pageRequest) {
//        Integer pageNum = pageRequest.getPageNum();
//        Integer pageSize = pageRequest.getPageSize();
//        String processDefId = pageRequest.getParam();
//        PageHelper.offsetPage((pageNum - 1) * pageSize, pageSize);
//        Page<CfTaskRole> page = (Page<CfTaskRole>) processTaskRoleMapper.getProcDefTaskRoles(processDefId);
//        return new PageResponse<>(page.getTotal(), pageNum, page.getResult());
//    }

    /**
     * 添加节点权限配置
     *
     * @param taskPosition
     */
    @Transactional(rollbackFor = Exception.class)
    public void addTaskRole(CfTaskPosition taskPosition) {
//        processTaskRoleMapper.insert(taskRole);
        cfTaskPositionMapper.insert(taskPosition);
    }

    /**
     * 修改节点权限配置
     *
     * @param taskPosition
     */
    @Transactional(rollbackFor = Exception.class)
    public void updateTaskRole(CfTaskPosition taskPosition) {
//        processTaskRoleMapper.updateTaskRole(taskRole);
        cfTaskPositionMapper.updateByPrimaryKey(taskPosition);
    }


    /**
     * 生成流程图
     *
     * @param processDefId
     * @param tenantId
     * @return
     */
    public byte[] generateDiagram(String processDefId, String tenantId) {
        InputStream inputStream = null;
        try {
            String[] keys = processDefId.split(":");
            ProcessDefinitionQuery processDefinitionQuery = repositoryService.createProcessDefinitionQuery().processDefinitionKey(keys[0]).processDefinitionVersion(Integer.parseInt(keys[1]));
            if (StringUtils.isNotBlank(tenantId)) {
                processDefinitionQuery.processDefinitionTenantId(tenantId);
            }
            ProcessDefinition processDefinition = processDefinitionQuery.singleResult();
            BpmnModel bpmnModel = repositoryService.getBpmnModel(processDefinition.getId());
            ProcessEngineConfiguration processEngineConfiguration = processEngine.getProcessEngineConfiguration();
            ProcessDiagramGenerator diagramGenerator = processEngineConfiguration.getProcessDiagramGenerator();
            inputStream = diagramGenerator.generateDiagram(bpmnModel, "PNG", new ArrayList<String>(), new ArrayList<String>(),
                    processEngineConfiguration.getActivityFontName(),
                    processEngineConfiguration.getLabelFontName(),
                    processEngineConfiguration.getAnnotationFontName(),
                    processEngineConfiguration.getClassLoader(),
                    1.0);
            return IOUtils.toByteArray(inputStream);
        } catch (Exception e) {
            throw new BizException(RtCodeEnum.R90000.getCode(), "生成流程图失败！");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    logger.error("关闭文件流失败！", e);
                }
            }
        }
    }
}
